﻿#include <QCoreApplication>
#include <QDateTime>
#include <QFile>
#include <QLibrary>
#include <QMutex>
#include <QSemaphore>
#include <QProcess>
#include <QSettings>
#include <QTextStream>
#include <qt_windows.h>
#include <QWaitCondition>
#include <QAbstractEventDispatcher>
#include <QAbstractNativeEventFilter>
#include <QVector>
#include <QThread>
#include <stdio.h>

#include "qtservicecontroller.h"
#include "qtservicecontroller_p.h"
#include "qtserviceprivate.h"

extern PRegisterServiceCtrlHandler pRegisterServiceCtrlHandler;
extern PSetServiceStatus pSetServiceStatus;
extern PChangeServiceConfig2 pChangeServiceConfig2;
extern PCloseServiceHandle pCloseServiceHandle;
extern PCreateService pCreateService;
extern POpenSCManager pOpenSCManager;
extern PDeleteService pDeleteService;
extern POpenService pOpenService;
extern PQueryServiceStatus pQueryServiceStatus;
extern PStartServiceCtrlDispatcher pStartServiceCtrlDispatcher;
extern PStartService pStartService;
extern PControlService pControlService;
extern PDeregisterEventSource pDeregisterEventSource;
extern PReportEvent pReportEvent;
extern PRegisterEventSource pRegisterEventSource;
extern PRegisterServiceProcess pRegisterServiceProcess;
extern PQueryServiceConfig pQueryServiceConfig;
extern PQueryServiceConfig2 pQueryServiceConfig2;

extern bool winServiceInit();


/*!
    \class QtServiceController

    \brief The QtServiceController class allows you to control
    services from separate applications.

    QtServiceController provides a collection of functions that lets
    you install and run a service controlling its execution, as well
    as query its status.

    In order to run a service, the service must be installed in the
    system's service database using the install() function. The system
    will start the service depending on the specified StartupType; it
    can either be started during system startup, or when a process
    starts it manually.

    Once a service is installed, the service can be run and controlled
    manually using the start(), stop(), pause(), resume() or
    sendCommand() functions.  You can at any time query for the
    service's status using the isInstalled() and isRunning()
    functions, or you can query its properties using the
    serviceDescription(), serviceFilePath(), serviceName() and
    startupType() functions. For example:

    \code
    MyService service;       \\ which inherits QtService
    QString serviceFilePath;

    QtServiceController controller(service.serviceName());

    if (controller.install(serviceFilePath))
        controller.start()

    if (controller.isRunning())
        QMessageBox::information(this, tr("Service Status"),
                                 tr("The %1 service is started").arg(controller.serviceName()));

    ...

    controller.stop();
    controller.uninstall();
    }
    \endcode

    An instance of the service controller can only control one single
    service. To control several services within one application, you
    must create en equal number of service controllers.

    The QtServiceController destructor neither stops nor uninstalls
    the associated service. To stop a service the stop() function must
    be called explicitly. To uninstall a service, you can use the
    uninstall() function.

    \sa QtServiceBase, QtService
*/

/*!
    \enum QtServiceController::StartupType
    This enum describes when a service should be started.

    \value AutoStartup The service is started during system startup.
    \value ManualStartup The service must be started manually by a process.

    \warning The \a StartupType enum is ignored under UNIX-like
    systems. A service, or daemon, can only be started manually on such
    systems with current implementation.

    \sa startupType()
*/

/*!
    Creates a controller object for the service with the given
    \a name.
*/
QtServiceController::QtServiceController(const QString &name)
 : d_ptr(new QtServiceControllerPrivate())
{
    Q_D(QtServiceController);
    d->q_ptr = this;
    d->serviceName = name;
}

/*!
    Destroys the service controller. This neither stops nor uninstalls
    the controlled service.

    To stop a service the stop() function must be called
    explicitly. To uninstall a service, you can use the uninstall()
    function.

    \sa stop(), QtServiceController::uninstall()
*/
QtServiceController::~QtServiceController()
{
    delete d_ptr;
}

/*!
    \fn bool QtServiceController::isInstalled() const

    Returns true if the service is installed; otherwise returns false.

    On Windows it uses the system's service control manager.

    On Unix it checks configuration written to QSettings::SystemScope
    using "QtSoftware" as organization name.

    \sa install()
*/
bool QtServiceController::isInstalled() const
{
    Q_D(const QtServiceController);
    bool result = false;
    if (!winServiceInit())
        return result;

    // Open the Service Control Manager
    SC_HANDLE hSCM = pOpenSCManager(0, 0, 0);
    if (hSCM) {
        // Try to open the service
        SC_HANDLE hService = pOpenService(hSCM, (wchar_t*)d->serviceName.utf16(),
                                          SERVICE_QUERY_CONFIG);

        if (hService) {
            result = true;
            pCloseServiceHandle(hService);
        }
        pCloseServiceHandle(hSCM);
    }
    return result;
}

/*!
    \fn bool QtServiceController::isRunning() const

    Returns true if the service is running; otherwise returns false. A
    service must be installed before it can be run using a controller.

    \sa start(), isInstalled()
*/
bool QtServiceController::isRunning() const
{
    Q_D(const QtServiceController);
    bool result = false;
    if (!winServiceInit())
        return result;

    // Open the Service Control Manager
    SC_HANDLE hSCM = pOpenSCManager(0, 0, 0);
    if (hSCM) {
        // Try to open the service
        SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(),
                                          SERVICE_QUERY_STATUS);
        if (hService) {
            SERVICE_STATUS info;
            int res = pQueryServiceStatus(hService, &info);
            if (res)
                result = info.dwCurrentState != SERVICE_STOPPED;
            pCloseServiceHandle(hService);
        }
        pCloseServiceHandle(hSCM);
    }
    return result;
}

/*!
    Returns the name of the controlled service.

    \sa QtServiceController(), serviceDescription()
*/
QString QtServiceController::serviceName() const
{
    Q_D(const QtServiceController);
    return d->serviceName;
}

/*!
    \fn QString QtServiceController::serviceDescription() const

    Returns the description of the controlled service.

    \sa install(), serviceName()
*/
QString QtServiceController::serviceDescription() const
{
    Q_D(const QtServiceController);
    QString result;
    if (!winServiceInit())
        return result;

    // Open the Service Control Manager
    SC_HANDLE hSCM = pOpenSCManager(0, 0, 0);
    if (hSCM) {
        // Try to open the service
        SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(),
             SERVICE_QUERY_CONFIG);
        if (hService) {
            DWORD dwBytesNeeded;
            char data[8 * 1024];
            if (pQueryServiceConfig2(
                    hService,
                    SERVICE_CONFIG_DESCRIPTION,
                    (unsigned char *)data,
                    8096,
                    &dwBytesNeeded)) {
                LPSERVICE_DESCRIPTION desc = (LPSERVICE_DESCRIPTION)data;
                if (desc->lpDescription)
                    result = QString::fromUtf16((const ushort*)desc->lpDescription);
            }
            pCloseServiceHandle(hService);
        }
        pCloseServiceHandle(hSCM);
    }
    return result;
}

/*!
    \fn QtServiceController::StartupType QtServiceController::startupType() const

    Returns the startup type of the controlled service.

    \sa install(), serviceName()
*/
QtServiceController::StartupType QtServiceController::startupType() const
{
    Q_D(const QtServiceController);
    StartupType result = ManualStartup;
    if (!winServiceInit())
        return result;

    // Open the Service Control Manager
    SC_HANDLE hSCM = pOpenSCManager(0, 0, 0);
    if (hSCM) {
        // Try to open the service
        SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(),
                                          SERVICE_QUERY_CONFIG);
        if (hService) {
            DWORD sizeNeeded = 0;
            char data[8 * 1024];
            if (pQueryServiceConfig(hService, (QUERY_SERVICE_CONFIG *)data, 8 * 1024, &sizeNeeded)) {
                QUERY_SERVICE_CONFIG *config = (QUERY_SERVICE_CONFIG *)data;
                result = config->dwStartType == SERVICE_DEMAND_START ? ManualStartup : AutoStartup;
            }
            pCloseServiceHandle(hService);
        }
        pCloseServiceHandle(hSCM);
    }
    return result;
}

/*!
    \fn QString QtServiceController::serviceFilePath() const

    Returns the file path to the controlled service.

    \sa install(), serviceName()
*/
QString QtServiceController::serviceFilePath() const
{
    Q_D(const QtServiceController);
    QString result;
    if (!winServiceInit())
        return result;

    // Open the Service Control Manager
    SC_HANDLE hSCM = pOpenSCManager(0, 0, 0);
    if (hSCM) {
        // Try to open the service
        SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(),
                                          SERVICE_QUERY_CONFIG);
        if (hService) {
            DWORD sizeNeeded = 0;
            char data[8 * 1024];
            if (pQueryServiceConfig(hService, (LPQUERY_SERVICE_CONFIG)data, 8 * 1024, &sizeNeeded)) {
                LPQUERY_SERVICE_CONFIG config = (LPQUERY_SERVICE_CONFIG)data;
                result = QString::fromUtf16((const ushort*)config->lpBinaryPathName);
            }
            pCloseServiceHandle(hService);
        }
        pCloseServiceHandle(hSCM);
    }
    return result;
}

/*!
    Installs the service with the given \a serviceFilePath
    and returns true if the service is installed
    successfully; otherwise returns false.

    On Windows service is installed in the system's service control manager with the given
    \a account and \a password.

    On Unix service configuration is written to QSettings::SystemScope
    using "QtSoftware" as organization name. \a account and \a password
    arguments are ignored.

    \warning Due to the different implementations of how services (daemons)
    are installed on various UNIX-like systems, this method doesn't
    integrate the service into the system's startup scripts.

    \sa uninstall(), start()
*/
bool QtServiceController::install(const QString &serviceFilePath, const QString &account,
                const QString &password)
{
    QStringList arguments;
    arguments << QLatin1String("-i");
    arguments << account;
    arguments << password;
    return (QProcess::execute(serviceFilePath, arguments) == 0);
}

/*!
    \fn bool QtServiceController::uninstall()

    Uninstalls the service and returns true if successful; otherwise returns false.

    On Windows service is uninstalled using the system's service control manager.

    On Unix service configuration is cleared using QSettings::SystemScope
    with "QtSoftware" as organization name.


    \sa install()
*/
bool QtServiceController::uninstall()
{
    Q_D(QtServiceController);
    bool result = false;
    if (!winServiceInit())
        return result;

    // Open the Service Control Manager
    SC_HANDLE hSCM = pOpenSCManager(0, 0, SC_MANAGER_ALL_ACCESS);
    if (hSCM) {
        // Try to open the service
        SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(), DELETE);
        if (hService) {
            if (pDeleteService(hService))
                result = true;
            pCloseServiceHandle(hService);
        }
        pCloseServiceHandle(hSCM);
    }
    return result;
}

/*!
    \fn bool QtServiceController::start(const QStringList &arguments)

    Starts the installed service passing the given \a arguments to the
    service. A service must be installed before a controller can run it.

    Returns true if the service could be started; otherwise returns
    false.

    \sa install(), stop()
*/
bool QtServiceController::start(const QStringList &args)
{
    Q_D(QtServiceController);
    bool result = false;
    if (!winServiceInit())
        return result;

    // Open the Service Control Manager
    SC_HANDLE hSCM = pOpenSCManager(0, 0, SC_MANAGER_CONNECT);
    if (hSCM) {
        // Try to open the service
        SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(), SERVICE_START);
        if (hService) {
            QVector<const wchar_t *> argv(args.size());
            for (int i = 0; i < args.size(); ++i)
                argv[i] = (const wchar_t*)args.at(i).utf16();

            if (pStartService(hService, args.size(), argv.data()))
                result = true;
            pCloseServiceHandle(hService);
        }
        pCloseServiceHandle(hSCM);
    }
    return result;
}

/*!
    \overload

    Starts the installed service without passing any arguments to the service.
*/
bool QtServiceController::start()
{
    return start(QStringList());
}

/*!
    \fn bool QtServiceController::stop()

    Requests the running service to stop. The service will call the
    QtServiceBase::stop() implementation unless the service's state
    is QtServiceBase::CannotBeStopped.  This function does nothing if
    the service is not running.

    Returns true if a running service was successfully stopped;
    otherwise false.

    \sa start(), QtServiceBase::stop(), QtServiceBase::ServiceFlags
*/
bool QtServiceController::stop()
{
    Q_D(QtServiceController);
    bool result = false;
    if (!winServiceInit())
        return result;

    SC_HANDLE hSCM = pOpenSCManager(0, 0, SC_MANAGER_CONNECT);
    if (hSCM) {
        SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(), SERVICE_STOP|SERVICE_QUERY_STATUS);
        if (hService) {
            SERVICE_STATUS status;
            if (pControlService(hService, SERVICE_CONTROL_STOP, &status)) {
                bool stopped = status.dwCurrentState == SERVICE_STOPPED;
                int i = 0;
                while(!stopped && i < 10) {
                    Sleep(200);
                    if (!pQueryServiceStatus(hService, &status))
                        break;
                    stopped = status.dwCurrentState == SERVICE_STOPPED;
                    ++i;
                }
                result = stopped;
            } else {
                qErrnoWarning(GetLastError(), "stopping");
            }
            pCloseServiceHandle(hService);
        }
        pCloseServiceHandle(hSCM);
    }
    return result;
}

/*!
    \fn bool QtServiceController::pause()

    Requests the running service to pause. If the service's state is
    QtServiceBase::CanBeSuspended, the service will call the
    QtServiceBase::pause() implementation. The function does nothing
    if the service is not running.

    Returns true if a running service was successfully paused;
    otherwise returns false.

    \sa resume(), QtServiceBase::pause(), QtServiceBase::ServiceFlags
*/
bool QtServiceController::pause()
{
    Q_D(QtServiceController);
    bool result = false;
    if (!winServiceInit())
        return result;

    SC_HANDLE hSCM = pOpenSCManager(0, 0, SC_MANAGER_CONNECT);
    if (hSCM) {
        SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(),
                             SERVICE_PAUSE_CONTINUE);
        if (hService) {
            SERVICE_STATUS status;
            if (pControlService(hService, SERVICE_CONTROL_PAUSE, &status))
                result = true;
            pCloseServiceHandle(hService);
        }
        pCloseServiceHandle(hSCM);
    }
    return result;
}

/*!
    \fn bool QtServiceController::resume()

    Requests the running service to continue. If the service's state
    is QtServiceBase::CanBeSuspended, the service will call the
    QtServiceBase::resume() implementation. This function does nothing
    if the service is not running.

    Returns true if a running service was successfully resumed;
    otherwise returns false.

    \sa pause(), QtServiceBase::resume(), QtServiceBase::ServiceFlags
*/
bool QtServiceController::resume()
{
    Q_D(QtServiceController);
    bool result = false;
    if (!winServiceInit())
        return result;

    SC_HANDLE hSCM = pOpenSCManager(0, 0, SC_MANAGER_CONNECT);
    if (hSCM) {
        SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(),
                             SERVICE_PAUSE_CONTINUE);
        if (hService) {
            SERVICE_STATUS status;
            if (pControlService(hService, SERVICE_CONTROL_CONTINUE, &status))
                result = true;
            pCloseServiceHandle(hService);
        }
        pCloseServiceHandle(hSCM);
    }
    return result;
}

/*!
    \fn bool QtServiceController::sendCommand(int code)

    Sends the user command \a code to the service. The service will
    call the QtServiceBase::processCommand() implementation.  This
    function does nothing if the service is not running.

    Returns true if the request was sent to a running service;
    otherwise returns false.

    \sa QtServiceBase::processCommand()
*/
bool QtServiceController::sendCommand(int code)
{
   Q_D(QtServiceController);
   bool result = false;
   if (!winServiceInit())
        return result;

    if (code < 0 || code > 127 || !isRunning())
        return result;

    SC_HANDLE hSCM = pOpenSCManager(0, 0, SC_MANAGER_CONNECT);
    if (hSCM) {
        SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(),
                                          SERVICE_USER_DEFINED_CONTROL);
        if (hService) {
            SERVICE_STATUS status;
            if (pControlService(hService, 128 + code, &status))
                result = true;
            pCloseServiceHandle(hService);
        }
        pCloseServiceHandle(hSCM);
    }
    return result;
}
